package encrypt

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"errors"
	"fmt"
)

// AesGcm 提供AES-GCM模式的加密解密功能。
// GCM(Galois/Counter Mode)提供认证加密功能。
type AesGcm struct{}

// Encrypt 使用AES-GCM模式加密字符串数据。
//
// 参数:
//   - src: 要加密的字符串
//   - key: 加密密钥字符串
//   - nonceIn: 加密用的 nonce, 如果为空, 就自动生成
//
// 返回值:
//   - []byte: 加密后的数据
//   - []byte: 实际使用的 nonce
//   - error: 加密过程中出现的错误
//
// 注意:
//   - 使用随机生成的12字节nonce
//   - 认证标签长度默认为16字节
//   - 返回的数据格式: nonce(12字节) + 密文 + 认证标签(16字节)
func (a *AesGcm) Encrypt(src, key string, nonceIn []byte) (ciphertext []byte, nonce []byte, err error) {
	// 将字符串密钥转换为字节切片
	keyBytes := []byte(key)

	// 创建AES cipher.Block
	block, err := aes.NewCipher(keyBytes)
	if err != nil {
		return nil, nil, fmt.Errorf("invalid AES key length: %w", err)
	}

	// 创建GCM模式的AEAD
	aead, err := cipher.NewGCM(block)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	nonce = nonceIn
	if len(nonce) == 0 {
		// 生成随机nonce（GCM标准推荐12字节）
		nonce = make([]byte, aead.NonceSize())
		if _, err := rand.Read(nonce); err != nil {
			return nil, nil, fmt.Errorf("failed to generate nonce: %w", err)
		}
	}

	// 加密并附加认证标签（不需要填充）
	ciphertext = aead.Seal(nil, nonce, []byte(src), nil)

	// 将nonce与密文合并（nonce在前）

	return
}

// Decrypt 使用AES-GCM模式解密字节数据。
//
// 参数:
//   - crypted: 加密数据(包含nonce和认证标签)
//   - key: 加密密钥字节切片
//   - nonceIn: 加密用的 nonce
//
// 返回值:
//   - []byte: 解密后的原始数据
//   - error: 解密过程中出现的错误
//
// 注意:
//   - 输入数据格式应为: nonce(12字节) + 密文 + 认证标签(16字节)
//   - 会验证认证标签以确保数据完整性
func (a *AesGcm) Decrypt(ciphertext, key, nonce []byte) ([]byte, error) {
	// 创建AES cipher.Block
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("invalid AES key length: %w", err)
	}

	// 创建GCM模式的AEAD
	aead, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	// 检查密文长度是否有效
	nonceSize := aead.NonceSize()
	if len(ciphertext) < nonceSize {
		return nil, errors.New("ciphertext too short")
	}

	// 解密并验证认证标签
	plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, fmt.Errorf("decryption failed: %w", err)
	}

	return plaintext, nil
}
